Skip to content

feat(groups): consolidate duplicate Geographic Area groups (BDMS-1143) - #821

Open
jirhiker wants to merge 4 commits into
stagingfrom
chore/BDMS-1143
Open

feat(groups): consolidate duplicate Geographic Area groups (BDMS-1143)#821
jirhiker wants to merge 4 commits into
stagingfrom
chore/BDMS-1143

Conversation

@jirhiker

Copy link
Copy Markdown
Member

Summary

BDMS-1143. Merges duplicate Geographic Area groups into the record that already represents the project, so one project is one row.

Adds data_migrations/migrations/20260810_0001_consolidate_geographic_area_groups.py, plus dry-run support in the data-migration framework, because a migration that deletes rows and re-points foreign keys should be reviewable before it writes.

oco data-migrations run 20260810_0001_consolidate_geographic_area_groups --dry-run

What the investigation changed about the ticket

The ticket assumes Geographic Area rows are duplicates of Monitoring Plans. Two findings complicate that, and both are encoded in the migration:

group_type is not provenance. cli/project_area_import.py matches only group_type == 'Geographic Area' and creates a row when it misses. transfers/group_transfer.py matches by name with no type filter and only upgrades a row to Monitoring Plan when one of its wells is currently monitored. Whichever ran first decides the surviving row's type, so a legacy NM_Aquifer project may live as a Monitoring Plan, as group_type = NULL, or as a Geographic Area. Merge targets are therefore any non-Geographic-Area group, and a target's group_type is left exactly as found.

Some Geographic Areas are not duplicates — they are the project. Where a Geographic Area's name is also a legacy project name, that row carries the project's identity and its well links. Both FKs into group are ON DELETE CASCADE, so merging one would take its wells with it. PROTECTED_NAMES refuses those outright, ahead of any manual mapping:

Albuquerque Basin · Colfax County · Eastern Tularosa Basin · Eddy County · Mimbres Basin · Quay County · Rio Rancho · San Miguel County · Torrance County

Worth noting Eastern Tularosa BasinTularosa Basin looks like an obvious merge by name. It would have destroyed a project.

How pairs were chosen

Not by name similarity. Monitoring Plan names come from legacy NM_Aquifer (Projects.csv); Geographic Area names come from the current ArcGIS layer (maps.nmt.edu/.../Water_Resources/MapServer/17). Different systems, so the names rarely align — exact normalized matching found 1 pair out of 46.

Instead, ProjectLocations.csv was joined to Location.csv and every legacy well tested point-in-polygon against each boundary. A pair qualifies only when both shares are high: the share of the project's wells inside the boundary and the share of the boundary's wells belonging to that project. High on one alone is containment, not identity — which is why San Juan BasinAnimas River was rejected despite scoring 96%.

Results on the dev database

Geographic Area rows 46 → 32. Fourteen consolidated: 1 by normalized name, 9 by name-only readings (abbreviations, qualifiers, word order), 4 by well membership.

Left for a human decision, documented in the module docstring:

  • Tiffany Fire contains both Tiffany Fire Recovery and Tiffany Fire Restoration at 100% with identical well counts — those two plans are duplicates of each other, a separate ticket.
  • Eastern Tularosa Basin and Northeastern Tularosa Basin both point at one Tularosa Basin; many-to-one merges are refused rather than half-applied.
  • Questa Area vs Questa Red River was merged on 81.8% / 77.1% well overlap.

The remaining 23 unmatched areas have no counterpart at all — counties and basins that are genuinely standalone. Together with the 9 protected rows, 32 of the original 46 should stay Geographic Areas. That answers the ticket's open question about retiring the group_type: it should not be retired.

Ordering note for staging and production

Run consolidation before 20260714_0001_publish_project_areas. The ogc_project_areas view filters release_status = 'public'; a boundary copied onto a target after that migration has run stays draft and drops out of the view until it is published again.

Also included

  • fix(build): domain/ was missing from [tool.setuptools] packages, which broke every oco command that reaches db.thing.
  • docs(agents): records the branch-off-staging rule from docs/release-flow.md.

Testing

16 tests covering the merge, geometry conflicts, unmatched and protected areas, untyped targets, contested targets, idempotency, and dry-run-writes-nothing. Full suite green, black and flake8 clean.


@ksmuczynski — could you verify and validate the projects list before this goes past staging? Two asks specifically:

  1. Confirm the merge pairs. The manual mappings in MANUAL_MATCHES are my reading of the evidence, not a decision anyone signed off on. The dry run prints every planned change with its source, so a review pass over that output is the check I would trust most.
  2. Work with AMP on the leftovers. Several things surfaced here that need someone who knows the projects: the Tiffany Fire Recovery / Tiffany Fire Restoration duplicate pair, the case-only duplicates Water Level Network / water Level Network, the two Tularosa Basin areas competing for one plan, and the 23 unmatched Geographic Areas — some of which may just be stale.

Nothing here has run outside the local dev database.

🤖 Generated with Claude Code

jirhiker and others added 4 commits August 10, 2026 20:19
The domain/ layer introduced by ADR4 was never added to
[tool.setuptools] packages, so it was missing from the installed
distribution. Any `oco` command reaching db.thing -> services.util ->
domain.units died with ModuleNotFoundError, which is most of the CLI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Data migrations that delete rows or re-point foreign keys need their
planned changes reviewed before anything is written, but run() took no
arguments and there was no way to preview one.

Adds an optional dry_run callable to DataMigration, a runner entry point
that checks the alembic gate, rolls back afterwards and never records
history, and a --dry-run flag on `oco data-migrations run`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BDMS-1143. A project can exist twice in the group table: once as the
project record and once as a Geographic Area carrying the boundary
geometry. The migration copies project_area onto the project record,
re-points linked things and child groups, then deletes the Geographic
Area. Both FKs into group are ON DELETE CASCADE, so re-pointing is
re-queried at apply time rather than replayed from the plan.

group_type turned out not to be provenance. project_area_import.py
matches only Geographic Area rows and creates one when it misses, while
group_transfer.py matches by name with no type filter and only upgrades a
row to Monitoring Plan when a well is currently monitored. Whichever ran
first decides the surviving row's type, so a legacy project may live as a
Monitoring Plan, as NULL, or as a Geographic Area. Merge targets are
therefore any non-Geographic-Area group, and the target's type is left as
found.

That collision also makes some deletions destructive: a Geographic Area
whose name is a legacy project name IS that project's row. PROTECTED_NAMES
refuses those outright, ahead of any manual mapping. Contested targets
claimed by two areas are reported rather than silently half-applied.

Manual pairs were chosen from well membership -- ProjectLocations.csv
joined to Location.csv, point-in-polygon against each boundary -- not from
name similarity, because Monitoring Plan names come from legacy
NM_Aquifer and Geographic Area names from the current ArcGIS layer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records the branching rule from docs/release-flow.md in the agent
playbook, including the hotfix exception.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jirhiker
jirhiker requested a review from ksmuczynski August 11, 2026 03:21
@ksmuczynski

Copy link
Copy Markdown
Contributor

@jirhiker Took a quick look this morning, will dig a little deeper this afternoon and reach out with questions.

@jirhiker

Copy link
Copy Markdown
Member Author

Thanks no rush. We should talk with Liz about this before any action is taken

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants